#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# @Project          :beginning-python
# @FileName         :chatserver.py
# @IDE              :PyCharm     
# @Time             :2022/5/31 20:48
# @Author           :Kermit Lee
# ==============================================

from asyncore import dispatcher
from asynchat import async_chat
import socket
import asyncore

PORT = 5005
NAME = 'TestChat'


class EndSession(Exception): pass


class CommandHandler:
    """
    类似标准库中cmd.Cmd的简单命令处理程序。
    """

    def unknown(self, session, cmd):
        """相应未知命令"""
        session.push(('Unknown command: {}s\r\n'.format(cmd)).encode())

    def handle(self, session, line):
        """处理从给定的会话中接收到的行。"""
        # print('before handle: ', type(line), line)
        # line = line.encode()
        # print('after handle: ', type(line), line)
        if not line.strip(): return
        # 分离命令
        parts = line.split(' ', 1)
        cmd = parts[0]
        try: line = parts[1].strip()
        except IndexError: line = ''
        # 试着查找处理程序
        meth = getattr(self, 'do_'+cmd, None)
        try:
            # 假定它是可以调用的
            # print('CommandHandler handle: 调用', 'do_' + cmd)
            meth(session, line)
        except TypeError:
            # 如果不可以被调用,此段代码响应未知命令
            # print('CommandHandler handle: 调用', cmd)
            self.unknown(session, cmd)


class Room(CommandHandler):
    """
    包括一个或多个用户（会话）的泛型环境。它负责基本的命令处理和广播。
    """

    def __init__(self, server):
        self.server = server
        self.sessions = []

    def add(self, session):
        """一个会话(用户)已经进入房间"""
        self.sessions.append(session)

    def remove(self, session):
        """一个会话(用户)已经离开房间"""
        self.sessions.remove(session)

    def broadcast(self, line):
        """向房间中的所有会话发送一行"""
        for session in self.sessions:
            session.push(line.encode())

    def do_logout(self, session, line):
        """响应logout命令"""
        raise EndSession


class LoginRoom(Room):
    """
    为刚刚连接上的用户准备的房间。
    """

    def add(self, session):
        Room.add(self, session)
        # 当用户进入时，问候他或她
        self.broadcast('Welcome to %s\r\n' % self.server.name)

    def unknown(self, session, cmd):
        # 所有未知命令 (除了login后者logout外的一切)
        # 会导致一个警告：
        session.push('please log in\nUse "login <nick>"\r\n'.encode())

    def do_login(self, session, line):
        name = line.strip()
        # 确保用户输入了名字
        if not name:
            session.push('Please enter a name\r\n'.encode())
        # 确保用户名没有被使用
        elif name in self.server.users:
            session.push(('The name "{}" is taken.\r\n'.format(name)).encode())
            session.push('Please try again.\r\n'.encode())
        else:
            # 名字没问题,所有存储在会话中,并且将用户移动到主聊天室。
            session.name = name
            session.enter(self.server.main_room)


class ChatRoom(Room):
    """
    为多用户相互聊天准备的房间。
    """

    def add(self, session):
        # 告诉所有人有新用户进入:
        self.broadcast(session.name + ' has entered the room.\r\n')
        self.server.users[session.name] = session
        super().add(session)

    def remove(self, session):
        Room.remove(self, session)
        # 告诉所有人有用户离开
        self.broadcast(session.name + ' has left the room.\r\n')

    def do_say(self, session, line):
        self.broadcast(session.name + ': ' + line + '\r\n')

    def do_look(self, session, line):
        """处理look命令,该命令用于查看谁在房间内"""
        session.push('The following are in this room:\r\n'.encode())
        for other in self.sessions:
            session.push((other.name + '\r\n').encode())

    def do_who(self, session, line):
        """处理who命令，该命令用于查看谁登录了"""
        session.push('The following are logged in:\r\n'.encode())
        for name in self.server.users:
            session.push((name + '\r\n').encode())


class LogoutRoom(Room):
    """
    为单用户准备的简单房间。只用于将用户名从服务器移除。
    """

    def add(self, session):
        # 当会话(用户)进入要删除的Loggoutroom时
        try: del self.server.users[session.name]
        except KeyError: pass


class ChatSession(async_chat):
    """
    单会话，负责和用户通信
    """

    def __init__(self, server, sock):
        super().__init__(sock)
        self.server = server
        self.set_terminator("\r\n".encode())
        self.data = []
        self.name = None
        # 所有的会话都开始于单独的LoginRoom中:
        self.enter(LoginRoom(server))

    def enter(self, room):
        # 当从房间移除自身(self),并且将自身添加到下一个房间
        try: cur = self.room
        except AttributeError: pass
        else: cur.remove(self)
        self.room = room
        room.add(self)

    def collect_incoming_data(self, data):
        # print('collect_incoming_data: ', data)
        self.data.append(data.decode())

    def found_terminator(self):
        line = ''.join(self.data)
        self.data = []
        # print('found_terminator: ', line)
        try: self.room.handle(self, line)
        except EndSession: self.handle_close()

    def handle_close(self):
        async_chat.handle_close(self)
        self.enter(LogoutRoom(self.server))


class ChatServer(dispatcher):
    """
    只有一个房间的聊天服务器。
    """

    def __init__(self, port, name):
        super().__init__()
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(('', port))
        self.listen(5)
        self.name = name
        self.users = {}
        self.main_room = ChatRoom(self)

    def handle_accept(self):
        conn, addr = self.accept()
        ChatSession(self, conn)


if __name__ == '__main__':
    s = ChatServer(PORT, NAME)
    try: asyncore.loop()
    except KeyboardInterrupt: print()
